﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using STPlatform.UniversalEntityV2;
using STPlatform.UPracticalMSSQLDatabaseV2;

namespace SimpleORM
{
    public abstract class CDObject : ICD
    {
        #region Methods

        #region Methods - Static

        public static object TryClone(object obj)
        {
            ICloneable ic = obj as ICloneable;
            if (ic != null)
                return ic.Clone();

            IList list = obj as IList;
            if (list != null)
                return list.Clone();

            return obj;
        }

        #endregion

        #region Methods - Virtual

        public virtual object Clone()
        {
            object newObj = MemberwiseClone();

            newObj.TraversalPropertiesInfo(ClonePropertyHandler, newObj);

            return newObj;
        }

        public virtual void Dispose()
        {
            //this.TraversalPropertiesInfo(DisposePropertyHandler);
        }

        #endregion

        #region Methods - Private

        private bool ClonePropertyHandler(PropertyInfo pi, object value, object target)
        {
            if (!pi.CanWrite)
                return true;

            pi.SetValue(target, CDObject.TryClone(value), null);

            return true;
        }

        private bool DisposePropertyHandler(string name, object value)
        {
            IDisposable id = value as IDisposable;
            if (id == null)
                return true;

            id.Dispose();

            return true;
        }

        #endregion

        #endregion
    }
    public enum eCacheMode
    {
        None = 0,
        OnlyGet = 1,
        OnlySet = 2,
        GetAndSet = 3,
    }
    public class DataSettings : CDObject
    {
        #region Properties

        public eCacheMode CacheMode { get; set; }

        #endregion

        #region Fields

        #endregion

        #region Ctor

        public DataSettings()
        {
            CacheMode = eCacheMode.GetAndSet;
        }

        #endregion
    }
    public interface ICD : ICloneable, IDisposable
    {
    }
    public class QuerySettings : DataSettings
    {
        #region Properties

        public bool AutoHandleException { get; set; }
        public bool PrintCommand { get; set; }
        public int? CommandTimeout { get; set; }
        public bool SaveInTransaction { get; set; }

        #endregion

        #region Fields

        #endregion

        #region Ctor

        public QuerySettings()
        {
            AutoHandleException = false;
            SaveInTransaction = true;
            //CommandTimeout = ConfigurationManager.AppSettings.TryGetValue<int>("DefaultDataCommandTimeout", 600);
            //CacheMode = ConfigurationManager.AppSettings.TryGetValue("DefaultDbContextCacheMode", eCacheMode.None);
            //PrintCommand = ConfigurationManager.AppSettings.TryGetValue("DefaultPrintSQL", false);
        }

        #endregion
    }
    public interface IProvider : ICD
    {
        #region Properties

        Guid ID { get; }
        string Name { get; }
        string ConnectionString { get; }

        #endregion

        #region Methods

        Exception Test();

        #endregion
    }
    public interface IProviderOpenable : IProvider
    {
        #region Properties

        bool IsOpen { get; }

        #endregion

        #region Methods

        bool Open();
        bool Close();

        #endregion
    }
    public interface IProviderDatabase : IProviderOpenable
    {
        #region Properties

        bool IsTransactionBegin { get; }
        bool IsSupportBatch { get; }
        bool IsSupportSchema { get; }

        #endregion

        #region Methods

        #region Methods - Transaction

        bool BeginTransaction();
        bool CommitTransaction();
        bool RollbackTransaction();

        #endregion

        #region Methods - Build

        IDCommand BuildScript(ICommandContext context);
        IDCommand BuildProcedure(ICommandContext context);

        #endregion

        #region Methods - Component

        ExpressionTranslater CreateExpressionTranslater();
        IScriptFactory CreateScriptFactory();
        DReader CreateDataReader();
        IDbSchema CreateSchema();

        #endregion

        #endregion
    }
    public interface IQuerybase : ICD
    {
        #region Properties

        IProviderDatabase DataSource { get; }
        ICommandContext CommandContext { get; }
        QuerySettings Settings { get; set; }

        #endregion

        #region Methods

        #region Methods - Execute

        //[OutputCommand]
        //[OpenConnection]
        //[CloseConnection]
        //[GetDbCache]
        //[SetDbCache]
        object Execute();

        //[OutputCommand]
        //[OpenConnection]
        //[CloseConnection]
        //[BeginTransaction]
        //[Commit]
        //[RollBack]
        //[GetDbCache]
        //[SetDbCache]
        object ExecuteInTransaction();

        //[OutputCommand]
        //[OpenConnection]
        //[CloseConnection]
        DataTable GetTable();

        //[OutputCommand]
        //[OpenConnection]
        //[CloseConnection]
        //[GetDbCache]
        //[SetDbCache]
        IList GetObjects(Type elementType);

        #endregion

        #region Methods - Cache

        void ClearCache();

        #endregion

        #endregion
    }
    public interface IQueryContext : IQueryable, IQuerybase
    {
        #region Methods

        #region Methods - Script

        //[ReBuildCommand(ExecuteCondition = eInvokeCondition.InvokeEnd)]
        //[OutputCommand(ExecuteCondition = eInvokeCondition.InvokeEnd)]
        void Compile();

        void Append(IQueryContext qc);

        IQueryContext Combine(IQueryContext qc);

        #endregion

        #endregion
    }
    class DbContextUnitOfWork : DbContext
    {
        private System.Collections.Hashtable tableQueryCache = System.Collections.Hashtable.Synchronized(new System.Collections.Hashtable());

        public DbContextUnitOfWork(string connectionStringName)
            : base(connectionStringName)
        {
            if (String.IsNullOrWhiteSpace(connectionStringName))
                throw new ArgumentNullException("connectionStringName");

            this.Settings.SaveInTransaction = true;
        }

        public IQueryable<T> GetQuery<T>() where T : class
        {
            return (IQueryable<T>)GetQuery(typeof(T));
        }

        public virtual IQueryable GetQuery(Type type)
        {
            return CreateQuery(type);
        }
        public new IQueryContext CreateQuery(Type type)
        {
            if (null == type)
                throw new ArgumentNullException("type");

            IQueryContext query = tableQueryCache[type] as IQueryContext;
            if (null == query)
            {
                IProviderDatabase p = Provider.Create(ProviderName, ConnectionString) as IProviderDatabase;
                QueryContext qc = Activator.CreateInstance(typeof(QueryContext<>).MakeGenericType(type), p) as QueryContext;

                qc.Settings = new QuerySettings(); ;
                query = qc as IQueryContext;
                tableQueryCache[type] = query;
            }

            return query;
        }
        public void Update(object entity)
        {
            if (null == entity)
                throw new ArgumentNullException("entity");

            if (entity == null)
                return;

            // 获取类型中定义的实体键属性
            var keyProps = GetKeyProperties(entity.GetType());
            if (null == keyProps || 1 > keyProps.Count)
                throw new ArgumentException("参数对应的类型 {0} 不包含任何实体键定义。");//.FormatWith(entity.GetType().FullName), "entity");

            // 获取所有键属性的值
            var keys = (from prop in keyProps
                        select prop.GetValue(entity, null)).ToArray();

            System.Text.StringBuilder predicateBuilder = new System.Text.StringBuilder();
            for (int i = 0; i < keyProps.Count; i++)
            {
                if (i > 0)
                    predicateBuilder.Append(" && ");

                predicateBuilder.AppendFormat("{0} == @{1}", keyProps[i].Name, i);
            }
            IQueryable query = GetQuery(entity.GetType());

            query = query.Where(predicateBuilder.ToString(), keys);

            query = query.Update(entity);
            AppendChange(query);
            // object ojb = SaveChanges();
        }
        public T Get<T>(params object[] keys) where T : class
        {
            if (null == keys)
                throw new ArgumentNullException("keys");

            return GetQuery<T>().Where(BuildKeyPredicate<T>(keys)).FirstOrDefault();
        }
        public void Add(object entity)
        {
            if (null == entity)
                throw new ArgumentNullException("entity");

            AppendChange(GetQuery(entity.GetType()).Add(entity));
        }

        public void Add<T>(T entity) where T : class
        {
            AppendChange(GetQuery<T>().Add(entity));
        }

        internal void AppendChange(IQueryable changeQuery)
        {
            if (changeQuery == null)
                return;

            Queries.Add(changeQuery);
        }

        public void Delete(object entity)
        {
            if (null == entity)
                throw new ArgumentNullException("entity");

            // 获取类型中定义的实体键属性
            var keyProps = GetKeyProperties(entity.GetType());
            if (null == keyProps || 1 > keyProps.Count)
                throw new ArgumentException(string.Format("参数对应的类型 {0} 不包含任何实体键定义。", entity.GetType().Name), "entity");

            // 获取所有键属性的值
            var keys = (from prop in keyProps
                        select prop.GetValue(entity, null)).ToArray();

            System.Text.StringBuilder predicateBuilder = new System.Text.StringBuilder();
            for (int i = 0; i < keyProps.Count; i++)
            {
                if (i > 0)
                    predicateBuilder.Append(" && ");

                predicateBuilder.AppendFormat("{0} == @{1}", keyProps[i].Name, i);
            }

            AppendChange(GetQuery(entity.GetType()).Where(predicateBuilder.ToString(), keys).Delete());
        }

        public void Delete<T>(params object[] keys) where T : class
        {
            if (null == keys)
                throw new ArgumentNullException("keys");

            AppendChange(GetQuery<T>().Where(BuildKeyPredicate<T>(keys)).Delete());
        }

        private IList<PropertyInfo> GetKeyProperties(Type type)
        {
            // 获取类型中定义的实体键属性
            var keyProps = (from prop in type.GetProperties()
                            where prop.IsDefined(typeof(Key2Attribute), true)
                            select prop).ToList();

            // 如果未找到定义的键属性则尝试使用名为 “ID” 的属性作为实体键
            if (null == keyProps || 1 > keyProps.Count)
                keyProps = (from prop in type.GetProperties()
                            where prop.Name.Equals("ID", StringComparison.OrdinalIgnoreCase)
                            select prop).ToList();

            return keyProps;
        }

        private Expression<Func<T, bool>> BuildKeyPredicate<T>(params object[] keys)
        {
            Type type = typeof(T);

            // 获取类型中定义的实体键属性
            var keyProps = GetKeyProperties(type);

            if (keys.Length != keyProps.Count)
                throw new ArgumentException("提供键值的数量与类型定义的键值数量不一致。", "keys");

            // 创建条件测试函数表达式
            ParameterExpression param = Expression.Parameter(type, "d");
            Expression body = null;
            var left = Expression.Property(param, keyProps[0].Name);
            var right = Expression.Constant(keys[0]);
            body = Expression.Equal(left, right);
            if (1 < keys.Length)
            {
                for (int i = 1; i < keys.Length; i++)
                {
                    left = Expression.Property(param, keyProps[i].Name);
                    right = Expression.Constant(keys[i]);
                    body = Expression.AndAlso(body, Expression.Equal(left, right));
                }
            }

            return Expression.Lambda<Func<T, bool>>(body, param);
        }
    }

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
    public class DataTypeAttribute : Attribute
    {
        #region Properties

        public eDataType Type { get; set; }

        #endregion

        #region Ctor

        public DataTypeAttribute(eDataType type)
        {
            Type = type;
        }

        #endregion

        #region Methods

        public static Dictionary<eDataType, MethodInfo> GetMethods(Type target)
        {
            Dictionary<eDataType, MethodInfo> dic = new Dictionary<eDataType, MethodInfo>();

            target.TraversalMethodsInfo(mi =>
            {
                var attr = mi.GetAttribute<DataTypeAttribute>();
                if (attr == null)
                    return true;

                dic[attr.Type] = mi;
                return true;
            });

            return dic;
        }

        #endregion
    }
    public interface IScriptFactory : ICommandContext
    {
        #region Methods

        #region Methods - Script

        IScriptFactory New();

        IScriptFactory Combine(IScriptFactory exp);
        void Append(IScriptFactory exp);

        IScriptFactory Concat(IScriptFactory exp);
        IScriptFactory Concat(IScriptFactory exp, string connector);
        IScriptFactory Bracket();
        IScriptFactory Stress();
        IScriptFactory Constant(object value);
        IScriptFactory Parameter(object value);

        IScriptFactory As(IScriptFactory exp);

        IScriptFactory Select();
        IScriptFactory Select(IScriptFactory exp);

        IScriptFactory Paging(IScriptFactory exp, IScriptFactory orderKey, eOrder order, int pageIndex, int pageSize);

        IScriptFactory Distinct(IScriptFactory exp);
        IScriptFactory Take(IScriptFactory exp);

        IScriptFactory Count();
        IScriptFactory Count(IScriptFactory exp);

        IScriptFactory Average(IScriptFactory exp);
        IScriptFactory Sum(IScriptFactory exp);
        IScriptFactory Max(IScriptFactory exp);
        IScriptFactory Min(IScriptFactory exp);

        IScriptFactory Len(IScriptFactory exp);

        IScriptFactory If(IScriptFactory condition, IScriptFactory ifTrue, IScriptFactory ifFalse);

        IScriptFactory Substring(IScriptFactory source, IScriptFactory target);
        IScriptFactory Replace(IScriptFactory source, IScriptFactory target);
        IScriptFactory ConvertToString();

        IScriptFactory From(IScriptFactory exp);
        IScriptFactory Where(IScriptFactory exp);

        IScriptFactory Join(IScriptFactory target, IScriptFactory condition);
        IScriptFactory JoinLeft(IScriptFactory target, IScriptFactory condition);

        IScriptFactory GroupBy(IScriptFactory exp);

        IScriptFactory Ascending(IScriptFactory exp);
        IScriptFactory Descending(IScriptFactory exp);
        IScriptFactory OrderBy(IScriptFactory exp);

        IScriptFactory Insert(IScriptFactory target, DParameter[] parameters);
        IScriptFactory Update(IScriptFactory target, IScriptFactory values);
        IScriptFactory Update(IScriptFactory target, DParameter[] parameters);
        IScriptFactory Delete(IScriptFactory exp);

        #endregion

        #region Methods - Object

        IScriptFactory SchemaObject(string schema, string objName);
        IScriptFactory CreateObject(string schema, string objName, DataColumnAttribute[] cols);
        IScriptFactory DropObject(string schema, string objName);
        IScriptFactory RenameObject(string schema, string objName, string objNameNew);

        IScriptFactory FormatProperty(DataColumnAttribute col);
        IScriptFactory CreateProperty(string schema, string objName, DataColumnAttribute col);
        IScriptFactory DropProperty(string schema, string objName, string colName);
        IScriptFactory AlterProperty(string schema, string objName, DataColumnAttribute col);

        #endregion

        #region Methods - Expression

        IScriptFactory And(IScriptFactory exp);
        IScriptFactory Or(IScriptFactory exp);

        IScriptFactory Equal(IScriptFactory exp);
        IScriptFactory NotEqual(IScriptFactory exp);
        IScriptFactory GreaterThan(IScriptFactory exp);
        IScriptFactory GreaterThanOrEqual(IScriptFactory exp);
        IScriptFactory LessThan(IScriptFactory exp);
        IScriptFactory LessThanOrEqual(IScriptFactory exp);
        IScriptFactory StartWith(IScriptFactory exp);
        IScriptFactory EndWith(IScriptFactory exp);
        IScriptFactory Contains(IScriptFactory exp);
        IScriptFactory Intersect(IScriptFactory exp);
        IScriptFactory Within(IScriptFactory exp);

        IScriptFactory Add(IScriptFactory exp);
        IScriptFactory Subtract(IScriptFactory exp);
        IScriptFactory Divide(IScriptFactory exp);
        IScriptFactory Multiply(IScriptFactory exp);

        #endregion

        #region Methods - Type

        string GetDataType(eDataType type, params object[] parameters);
        eDataType GetDataType(string type);

        [DataType(eDataType.Boolean)]
        string GetBooleanType(params object[] parameters);
        [DataType(eDataType.Float)]
        string GetFloatType(params object[] parameters);
        [DataType(eDataType.Double)]
        string GetDoubleType(params object[] parameters);
        [DataType(eDataType.Decimal)]
        string GetDecimalType(params object[] parameters);
        [DataType(eDataType.Byte)]
        string GetByteType(params object[] parameters);
        [DataType(eDataType.Int16)]
        string GetInt16Type(params object[] parameters);
        [DataType(eDataType.Int32)]
        string GetInt32Type(params object[] parameters);
        [DataType(eDataType.Int64)]
        string GetInt64Type(params object[] parameters);
        [DataType(eDataType.String)]
        string GetStringType(params object[] parameters);
        [DataType(eDataType.Guid)]
        string GetGuidType(params object[] parameters);
        [DataType(eDataType.DateTime)]
        string GetDateTimeType(params object[] parameters);
        [DataType(eDataType.Binary)]
        string GetBinaryType(params object[] parameters);
        [DataType(eDataType.Object)]
        string GetObjectType(params object[] parameters);
        //[DataType(eDataType.Geometry)]
        //string GetGeometryType(params object[] parameters);

        #endregion

        #endregion
    }
    public enum eOrder
    {
        Ascending,
        Descending
    }
}
